--[[
	Broker_CPU, display CPU/Memory usage, Framerate, Latency for World of Warcraft
	Copyright (C) 2010 Arnaud Dovi (ad@heapoverflow.com)

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
]]

-------------------------------------------------------------------------
-----------------Simple Code Profiling inside WoW------------------------
---------debug garbage memory and cpu usage in any piece of code---------
-------------------------------------------------------------------------
-- local addonId -- anywhere up in the code (a local "global" to your code)
-- local function getAddOnId() -- anywhere up in the code
	-- for i = 1, GetNumAddOns() do
		-- if GetAddOnInfo(i) == "Broker_CPU" then addonId = i end -- update to your addon name
	-- end
-- end
-- local function profileMem(id, start, MEMstart) -- anywhere up in the code
	-- if start then
		-- collectgarbage("collect")
		-- return GetAddOnMemoryUsage(id, "Brok_erCPU") -- you can remove BrokerCPU strings they are used internally, don't simple profile with the brokercpu on, you will most likely conflict with it
	-- else
		-- UpdateAddOnMemoryUsage("BrokerCPU")
		-- return GetAddOnMemoryUsage(id, "Brok_erCPU") - MEMstart
	-- end
-- end
-- local function profileCpu(id, start, CPUstart) -- anywhere up in the code
	-- if start then
		-- return GetAddOnCPUUsage(id, "Brok_erCPU")
	-- else
		-- UpdateAddOnCPUUsage("BrokerCPU")
		-- return GetAddOnCPUUsage(id, "Brok_erCPU") - CPUstart
	-- end
-- end
--getAddOnId() -- at the start of your mod to store addon id
--local MEMstart = profileMem(addonId, true) -- copy paste BEFORE the code you want to profile for garbage memory production or cpu usage
--local t1end = profileMem(addonId, false, MEMstart) -- copy paste AFTER the code you want to profile for garbage memory production or cpu usage
--print("MEM-PROF1:"..(t1end > 0 and t1end or 0)) -- copy paste AFTER the code you want to profile for garbage memory production or cpu usage, the returned number is the amount of the garbage between start and end
------------------------------------------------------------------------------
------------------------------------------------------------------------------

if not LibStub then error("Broker_CPU requires LibStub") end
local obj, QTip, minimap, event, first, paused
local lastotalcpu, lastotalmem, lastlat, lastlatb, scriptProfile, db
local totalcpu, totaldiff, totalmem, totalmemdiff, activeId = 0, 0, 0, 0, 0
local larg1, larg2, larg3, larg4
local numTotalAddOns, numActiveAddOns

local modsort, moddata, stringbin    = {}, {}, {}
local icon                           = "Interface\\AddOns\\Broker_CPU\\icon"
local arrowa                         = "Interface\\AddOns\\Broker_CPU\\arrowa"
local arrowb                         = "Interface\\AddOns\\Broker_CPU\\arrowb"
local arrowc                         = "Interface\\AddOns\\Broker_CPU\\arrowc"
local MINOR_VERSION                  = tonumber(("$Revision: 12 $"):match("%d+"))
local frFR                           = GetLocale() == "frFR"
local fps_                           = frFR and "ips" or "fps"
local kb                             = frFR and "Ko"  or "KB"
local mb                             = frFR and "Mo"  or "MB"
local newAddOn, firstLoad            = false, true

local collectgarbage = collectgarbage
local math_modf      = math.modf
local print          = print
local table_sort     = table.sort
local tonumber       = tonumber
local print          = print
local pairs          = pairs
local type           = type
local _G             = _G

local CreateFrame   = CreateFrame
local GetAddOnInfo  = GetAddOnInfo
local GetFramerate  = GetFramerate
local GetNetStats   = GetNetStats
local GetNumAddOns  = GetNumAddOns
local IsAddOnLoaded = IsAddOnLoaded

-- for secure hooks to stop mods from using APIs they are not supposed to touch while Broker_CPU is running
local orig_GetAddOnCPUUsage       = GetAddOnCPUUsage
local orig_GetAddOnMemoryUsage    = GetAddOnMemoryUsage
local orig_GetScriptCPUUsage      = GetScriptCPUUsage
local orig_ResetCPUUsage          = ResetCPUUsage
local orig_UpdateAddOnCPUUsage    = UpdateAddOnCPUUsage
local orig_UpdateAddOnMemoryUsage = UpdateAddOnMemoryUsage

local L = {
	COLOR_ACE3    = "|cff33ff99",
	COLOR_ADDON   = "|cff00ffff",
	COLOR_CPU     = "|cff0095dd",
	COLOR_GOLD    = "|cfffed100",
	COLOR_GREEN   = "|cff33ff33",
	COLOR_GREY    = "|cff808080",
	COLOR_HINT1   = "|cffeda55f",
	COLOR_MEM     = "|cff0095dd",
	COLOR_RED     = "|cffff0000",
	COLOR_STOP    = "|r",
	COLOR_SUFF    = "|cfffed100",
	EXPANDED      = "The tooltip goes out of screen, autoresizing to show 5 AddOns.",
	LINE_STOP     = "\n",
	NOT_DBICO     = "(requires LibDBIcon-1.0)",
	NOT_QTIP      = "(requires LibQTip-1.0)",
	N_BLI         = "Total & Blizzard",
	N_CPU         = "CPU Profiling",
	N_DAT_LDB     = "LDB display",
	N_DAT_TIP     = "Tooltips",
	N_DET         = "Detach",
	N_FPS         = "Framerate",
	N_GAR         = "Garbage",
	N_HELP        = "Help",
	N_HID         = "Hide",
	N_ICO         = "Icons",
	N_LAT         = "Latency",
	N_LIB         = "Libraries",
	N_L_ICO       = "LibDBIcon",
	N_L_QTIP      = "LibQTip",
	N_MAP         = "Minimap",
	N_MEM         = "Memory",
	N_MOD         = "AddOns",
	N_SC          = "Scale",
	N_THR         = "Thresholds",
	N_THR_LDB     = "Broker text",
	N_THR_TIP     = "Tooltip text",
	N_TITLE       = "BrokerCPU",
	N_TOT         = "Total",
	N_thr         = "threshold",
	Q_NAME        = "BrokerCPUQTip",
	SHOW_HELP     = "show command lines",
	SLASH_SEP     = " - ",
	TIPHINT1      = "%sFor cpu usage, the mod requires%scpu profiling enabled.%s",
	TIPHINT2      = "%sRemember to switch the profiling off%swhen you don't need it.%s",
	TIPHINT3      = "%sFor memory usage, just enable the%soption.%s",
	TIPHINT4      = "%sCtrl-Alt-LClick%s %sto enable/disable CPU%sprofiling.%s",
	TIPHINT5      = "%sType%s %s/brokercpu%s %sfor more usages.%s",
	TOGGLE_CPU    = "Toggle the client CPU profiling.",
	TOGGLE_DET    = "Toggle detached tooltip.",
	TOGGLE_FPS    = "Toggle framerate collection in %s.",
	TOGGLE_GAR    = "Force the client to collect the garbage memory",
	TOGGLE_HID    = "Hide a detached QTip. (or Shift+Click the Broker icon)",
	TOGGLE_ICO    = "Toggle the arrow icons.",
	TOGGLE_KLL    = "Set a maximum number of AddOns to show.",
	TOGGLE_LAT    = "Toggle latency collection in %s.",
	TOGGLE_LDB    = "Threshold (in seconds) of the broker text update.",
	TOGGLE_L_ICO  = "Toggle the use of LibDBIcon.",
	TOGGLE_L_QTIP = "Toggle between the use of LibQTip or GameTooltip.",
	TOGGLE_MAP    = "Toggle minimap icon.",
	TOGGLE_MEM    = "Toggle memory collection in %s.",
	TOGGLE_RST    = "Reset the AddOn to default settings.",
	TOGGLE_SC     = "Change the qtip scale.",
	TOGGLE_TIP    = "Threshold (in seconds) of the tooltip update."
}


local function D(...)
	print(...)
end

-- it forces the mod to not produce garbage in loops with stable strings (ie addon names, ranks, ...)
-- but don't do it with unique strings (ie cpu, mem...)or it will produce garbage
-- it saves from producing a notable amount of garbage memory to each loops with stable concatened strings
-- this could have a negative impact if you don't understand and use it wrongly
local function stringRecycler(...)
	stringbin[...] = ...
	return stringbin[...]
end

local function newUpdateAddOnCPUUsage(addon)
	return addon == L.N_TITLE and orig_UpdateAddOnCPUUsage() or nil
end

local function newUpdateAddOnMemoryUsage(addon)
	return addon == L.N_TITLE and orig_UpdateAddOnMemoryUsage() or nil
end

local function newGetAddOnCPUUsage(id, addon)
	return addon == L.N_TITLE and orig_GetAddOnCPUUsage(id) or 0
	--return addon == L.N_TITLE and random(1,5000) or orig_GetAddOnMemoryUsage(id) -- for code profiling
end

local function newGetAddOnMemoryUsage(id, addon)
	return addon == L.N_TITLE and orig_GetAddOnMemoryUsage(id) or 0
	--return addon == L.N_TITLE and random(1,5000) or orig_GetAddOnMemoryUsage(id) -- for code profiling
end

local function newGetScriptCPUUsage(addon)
	return addon == L.N_TITLE and orig_GetScriptCPUUsage(addon) or 0
end

local function newResetCPUUsage(addon)
	return addon == L.N_TITLE and orig_ResetCPUUsage(addon) or 0
end

local isBadassToBlacklist = { -- AddOns not supposed to use UpdateAddOnCPUUsage or UpdateAddOnMemoryUsage that can break legitimate cpu mem reporters
	Carbonite = true,
	Healbot   = true
}

local function formatTime(n)
	local inSecond = n > 1000 or nil
	return ("%.1f"):format(not inSecond and n or n * (1/1000) or 0), inSecond and "s" or "ms"
end

local function formatMemory(n, ldbtext)
	local pattern = ldbtext and "%.1f" or "%.2f"
	local low     = n < 1024 or nil
	return (pattern):format(low and n or n * (1/1024) or 0), low and kb or mb
end

-- Tekkub's ColorGradient() http://www.wowwiki.com/ColorGradient
local function colorGradient(perc, r1, g1, b1, r2, g2, b2, r3, g3, b3)
	if perc >= 1 then return r3, g3, b3 elseif perc <= 0 then return r1, g1, b1 end
	local segment, relperc = math_modf(perc*2)
	if segment == 1 then r1, g1, b1, r2, g2, b2 = r2, g2, b2, r3, g3, b3 end
	return r1 + (r2-r1)*relperc, g1 + (g2-g1)*relperc, b1 + (b2-b1)*relperc
end

local function onSort(x, y)
	if x and x.cpu then return x.cpu > (y.cpu or 0)
	elseif x and x.mem then return x.mem > (y.mem or 0) end
end

-- I forgot to include that check in first Release
-- sorry for messing up your tips ;D
local function isOwnTip(GameTipOrQTip)
	if GameTipOrQTip and GameTipOrQTip:IsVisible() then
		if QTip and QTip:IsAcquired(L.Q_NAME) then return true end
		local sig = _G.GameTooltipTextRight1:GetText() or ""
		if (sig):find(L.N_TITLE) then return true end
	end
	return false
end

local function doItForAllorActiveMods(i, all, isTip, light)
	if light == true then
		if not all and moddata[i] or (all and IsAddOnLoaded(i)) then
			moddata[i]        = moddata[i] or {}
			moddata[i].name   = not newAddOn and moddata[i].name or GetAddOnInfo(i)
			moddata[i].oldcpu = scriptProfile and newGetAddOnCPUUsage(i, L.N_TITLE) or false
			moddata[i].oldmem = db.memory and newGetAddOnMemoryUsage(i, L.N_TITLE) or false
		end
		return
	end
	if not all and moddata[i] or (all and IsAddOnLoaded(i)) then
		local cpuused, cpudiff, memused, memdiff
		activeId          = activeId + 1
		moddata[i]        = moddata[i] or {}
		moddata[i].name   = not newAddOn and moddata[i].name or GetAddOnInfo(i)
		
		if scriptProfile then
			cpuused  = newGetAddOnCPUUsage(i, L.N_TITLE)
			totalcpu = totalcpu + cpuused
		end
		if db.memory or db.memory_ldb then
			memused  = newGetAddOnMemoryUsage(i, L.N_TITLE)
			totalmem = totalmem + memused
		end
		if isTip then
			if db.memory and QTip then
				memdiff           = memused - (moddata[i].oldmem or memused)
				totalmemdiff      = totalmemdiff + memdiff
				moddata[i].oldmem = (db.tooltipthreshold <= 2) and memused or false
			end
			if scriptProfile and QTip then
				cpudiff           = cpuused - (moddata[i].oldcpu or cpuused)
				totaldiff         = totaldiff + cpudiff
				moddata[i].oldcpu = (db.tooltipthreshold <= 2) and cpuused or false
			end
			modsort[activeId]         = modsort[activeId] or {}
			modsort[activeId].add     = moddata[i].name
			modsort[activeId].cpu     = ((QTip and scriptProfile) or not (QTip or db.memory)) and cpuused or false
			modsort[activeId].mem     = db.memory and memused or false
			modsort[activeId].cpudiff = (scriptProfile and QTip) and cpudiff or false
			modsort[activeId].memdiff = (db.memory and QTip) and memdiff or false
		end
	end
end

local function onCollect(c)
	local isTip = c == true
	if isTip and QTip then
		-- if the tooltip threshold is default or low lighten the collect
		-- this gives the same results for less cpu work.
		if db.tooltipthreshold > 2 then
			if not newAddOn then
				for id, data in pairs(moddata) do
					doItForAllorActiveMods(id, false, isTip, true)
				end
			else
				for i = 1, numTotalAddOns do
					doItForAllorActiveMods(i, true, isTip, true)
				end
			end
		end
	end
	totalcpu, totaldiff, totalmem, totalmemdiff, activeId = 0, 0, 0, 0, 0
	if scriptProfile then newUpdateAddOnCPUUsage(L.N_TITLE) end
	if db.memory or db.memory_ldb then newUpdateAddOnMemoryUsage(L.N_TITLE) end
	if not newAddOn then
		for id, data in pairs(moddata) do
			doItForAllorActiveMods(id, false, isTip, nil)
		end
	else
		for i = 1, numTotalAddOns do
			doItForAllorActiveMods(i, true, isTip, nil)
		end
	end
	if activeId and activeId ~= (numActiveAddOns or 0) then first = true end
	numActiveAddOns  = activeId
	newAddOn         = false
	lastotalcpu      = totalcpu
	lastotalmem      = totalmem
	return lastotalcpu, totaldiff, lastotalmem, totalmemdiff
end

local function OnLeave(self)
	local self = self
	local GameTip = obj.onupdate or nil
	if GameTip then
		if GameTip:GetScript("OnUpdate") then GameTip:SetScript("OnUpdate", nil) end
		obj.onupdate = nil
	end
	if self and self.tooltip then
		if self.tooltip:GetScript("OnUpdate") then self.tooltip:SetScript("OnUpdate", nil) end
		QTip:Release(self.tooltip)
		self.tooltip = nil
		obj.display = nil
	end
	if not obj.OnLeave and not (scriptProfile and db.memory) then obj.OnLeave = OnLeave end
	return
end

local function OnDetachTooltip(self)
	local tip = self.tooltip
	if not db.detach then
		tip:SmartAnchorTo(self)
		if not obj.OnLeave then obj.OnLeave = OnLeave end
		if tip:GetScript("OnMouseDown") then tip:SetScript("OnMouseDown", nil) end
		if tip:GetScript("OnMouseUp") then tip:SetScript("OnMouseUp", nil) end
	else
		if not db.detacheddata then
			db.detacheddata = {}
			db.detacheddata.pt = "CENTER"
			db.detacheddata.rpt = "CENTER"
			db.detacheddata.x, db.detacheddata.y = 0, 0
		end
		tip:ClearAllPoints()
		tip:SetFrameStrata("FULLSCREEN")
		tip:EnableMouse(true)
		tip:SetResizable(true)
		tip:SetFrameLevel(1)
		tip:SetMovable(true)
		tip:SetClampedToScreen(true)
		if not ( tip:GetScript("OnMouseDown") and tip:GetScript("OnMouseUp") ) then
			tip:SetScript("OnMouseDown", function()
				paused = true
				tip:StartMoving()
			end)
			tip:SetScript("OnMouseUp", function()
				tip:StopMovingOrSizing()
				db.detacheddata.pt, _, db.detacheddata.rpt, db.detacheddata.x, db.detacheddata.y = tip:GetPoint()
				paused = false
			end)
		end
		obj.OnLeave = nil
	end
	return
end

local function recycleQTip(qtip, col)
	local cols = #qtip.columns
	local num  = col - cols
	if num > 0 then
		for i = 1, num do
			qtip:AddColumn("LEFT")
		end
	else
		for i = 1 + ( cols + num ), cols do
			qtip.columns[i]:ClearAllPoints()
			qtip.columns[i]:Hide()
			qtip.columns[i] = nil
		end
	end
end

local function doesQTipShouldResize(qtip)
	local size  = #qtip.columns
	if scriptProfile and db.memory then
		if db.icons then
			if qtip.columns and size ~= 11 then return recycleQTip(qtip, 11) end
		else
			if qtip.columns and size ~= 7 then return recycleQTip(qtip, 7) end
		end
	elseif scriptProfile or db.memory then
		if db.icons then
			if qtip.columns and size ~= 7 then return recycleQTip(qtip, 7) end
		else
			if qtip.columns and size ~= 5 then return recycleQTip(qtip, 5) end
		end
	end
end

local function DoesTheTipExpandsTooMuch(qtip)
	local parenth = (_G.UIParent:GetHeight()) or 1
	local tiph = (qtip:GetHeight() * db.scale) or 0
	if tiph > parenth then
		D(("%s%s%s: %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, L.EXPANDED))
		db.cpukill = 5
		first = true
		return true
	end
	return false
end

local function OnQTipShow(self)
	if ( self and self.tooltip ) or not QTip then return end -- secure check
	-- Acquiring 5 columns the minimum required and then adding/deleting columns upon demands to avoid
	-- creating fantom cells and increasing QTip jobs
	if not scriptProfile and not db.memory then
		local tip = QTip:Acquire(L.Q_NAME, 1)
		self.tooltip = tip
		local tt = tip:AddLine()
		--if db.scale and db.scale ~= tip:GetScale() then tip:SetScale(db.scale) end
		tip:Show()
		tip:SetScale(1)
		tip:SetCell(tt, 1, (L.TIPHINT1):format(L.COLOR_GOLD, L.LINE_STOP, L.COLOR_STOP))
		tt = tip:AddLine()
		tip:SetCell(tt, 1, (L.TIPHINT2):format(L.COLOR_GOLD, L.LINE_STOP, L.COLOR_STOP))
		tt = tip:AddLine()
		tip:SetCell(tt, 1, (L.TIPHINT3):format(L.COLOR_GOLD, L.LINE_STOP, L.COLOR_STOP))
		tip:AddLine(" ")
		tt = tip:AddLine()
		tip:SetCell(tt, 1, (L.TIPHINT4):format(L.COLOR_HINT1, L.COLOR_STOP, L.COLOR_GREEN, L.LINE_STOP, L.COLOR_STOP))
		tt = tip:AddLine()
		tip:SetCell(tt, 1, (L.TIPHINT5):format(L.COLOR_GREEN, L.COLOR_STOP, L.COLOR_HINT1, L.COLOR_STOP, L.COLOR_GREEN, L.COLOR_STOP))
		return tip:SmartAnchorTo(self)
	end
	local tip = QTip:Acquire(L.Q_NAME, 5)
	obj.display = self
	self.tooltip = tip
	local delay = 0
	first = true
	tip:Show()
	tip:SetScript("OnUpdate", function(_, elapsed)
		delay = delay + elapsed
		if not paused and ( delay > tonumber(db.tooltipthreshold) ) or first then
			delay = 0
			if not ( scriptProfile or db.memory ) or not isOwnTip(tip) then OnLeave(self) return end
			local totcpu, totdiff, totmem, totmemdiff = onCollect(true)
			local kill = tonumber(db.cpukill) or 0
			table_sort(modsort, onSort)
			if first then
				-- To reduce QTip's CPU usages, anything executed in this if chain happens only once,
				-- they are stable and not variable datas so re-creating them to each loops increases
				-- a lot QTip cpu usages for nothing helpful.
				if tip.lines[1] then tip:Clear() end
				doesQTipShouldResize(tip) -- column recycling
				OnDetachTooltip(self)
				local tt
				for k in pairs(modsort) do
					tt = tip:AddLine(" ")
					if tt == kill then break end
				end
				tip:AddLine(" ")
				tt = tip:AddLine()
				tip:SetCell(tt, 1, stringRecycler(L.COLOR_GOLD..L.N_TOT..L.COLOR_STOP), nil, nil, 3)
				if db.memory then
					tt = tip:AddLine()
					tip:SetCell(tt, 1, stringRecycler(L.COLOR_GOLD..L.N_BLI..L.COLOR_STOP), nil, nil, 3)
				end
				if db.framerate or db.latency then
					tip:AddLine(" ")
					if db.framerate then
						tt = tip:AddLine()
						tip:SetCell(tt, 1, stringRecycler(L.COLOR_GOLD..L.N_FPS..L.COLOR_STOP), nil, nil, 4)
					end
					if db.latency then
						tt = tip:AddLine()
						tip:SetCell(tt, 1, stringRecycler(L.COLOR_GOLD..L.N_LAT..L.COLOR_STOP), nil, nil, 4)
					end
				end
			end
			-- auto-resizing to a decent number of addons for noobs like yess ;p
			if DoesTheTipExpandsTooMuch(tip) then return end
			first = false
			if db.scale and db.scale ~= tip:GetScale() then tip:SetScale(db.scale) end
			local tt = 0
			local col = 4
			for k, v in pairs(modsort) do
				tt = tt + 1
				tip:SetCell(tt, 1, stringRecycler(L.COLOR_ADDON..tt..L.COLOR_STOP))
				tip:SetCell(tt, 2, " ")
				tip:SetCell(tt, 3, stringRecycler(L.COLOR_ADDON..v.add..L.COLOR_STOP))
				if scriptProfile then
					-- instead of padding with a column, using a variable to avoid QTip resizes
					local str, suf = formatTime(v.cpu) -- garbage creation
					local r, g, b = colorGradient((v.cpudiff * (1/10)) * (1/10), 0, 1, 0, 1, 1, 0, 1, 0, 0)
					local rem = (str):len() - 5
					local pad = ("            "):sub(1 + rem * 2)
					if v.cpudiff == 0 then
						tip:SetCell(tt, 4, ("%s%s%s %s%s%s"):format(L.COLOR_CPU, pad, str, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
					else
						tip:SetCell(tt, 4, ("|cff%02x%02x%02x%s%s %s%s%s"):format(r*255, g*255, b*255, pad, str, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
					end
					local str = ("%.2f"):format(v.cpudiff * (1/10))
					local rem = (str):len() - 4
					local pad = ("      "):sub(1 + rem * 2)
					tip:SetCell(tt, 5, ("|cff%02x%02x%02x%s%s %s%%%s"):format(r*255, g*255, b*255, pad, str, L.COLOR_SUFF, L.COLOR_STOP), nil, "RIGHT")
					if db.icons then
						tip:SetCell(tt, 6, "  ")
						tip:SetCell(tt, 7, "    ")
						local icon = (v.cpudiff == 0) and arrowc or (v.cpudiff > 0) and arrowa or (v.cpudiff < 0) and arrowb
						tip.lines[tt].cells[7]:SetBackdrop({bgFile = icon})
						if not tip.lines[tt].cells[7]:GetScript("OnHide") then
							tip.lines[tt].cells[7]:SetScript("OnHide", function(self) self:SetBackdrop(nil) self:SetScript("OnHide", nil) end)
						end
						col = 8
					else
						col = 6
					end
				end
				if db.memory then
					local str, suf = formatMemory(v.mem)
					local r, g, b = colorGradient((v.memdiff * (1/10)) * (1/10), 0, 1, 0, 1, 1, 0, 1, 0, 0)
					local rem = (str):len() - 5
					local pad = ("          "):sub(1 + rem * 2)
					if v.memdiff == 0 then
						tip:SetCell(tt, col, ("%s%s%s %s%s%s"):format(L.COLOR_MEM, pad, str, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
					else
						tip:SetCell(tt, col, ("|cff%02x%02x%02x%s%s %s%s%s"):format(r*255, g*255, b*255, pad, str, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
					end
					local str = ("%.2f"):format(v.memdiff * (1/10))
					local rem = (str):len() - 4
					local pad = ("      "):sub(1 + rem * 2)
					tip:SetCell(tt, col+1, ("|cff%02x%02x%02x%s%s %s%%%s"):format(r*255, g*255, b*255, pad, str, L.COLOR_SUFF, L.COLOR_STOP), nil, "RIGHT")
					if db.icons then
						tip:SetCell(tt, col+2, "  ")
						tip:SetCell(tt, col+3, "    ")
						local icon = (v.memdiff == 0) and arrowc or (v.memdiff > 0) and arrowa or (v.memdiff < 0) and arrowb
						tip.lines[tt].cells[col+3]:SetBackdrop({bgFile = icon})
						if not tip.lines[tt].cells[col+3]:GetScript("OnHide") then
							tip.lines[tt].cells[col+3]:SetScript("OnHide", function(self) self:SetBackdrop(nil) self:SetScript("OnHide", nil) end)
						end
					end
				end
				if tt == kill then break end
			end
			tt = tt + 2
			if scriptProfile then
				local totcpu, suf = formatTime(totcpu)
				tip:SetCell(tt, 4, ("%s%s %s%s%s"):format(L.COLOR_GOLD, totcpu, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
				local r, g, b = colorGradient((totdiff * (1/10)) * (1/20), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				tip:SetCell(tt, 5, ("|cff%02x%02x%02x%.2f %s%%%s"):format(r*255, g*255, b*255, totdiff * (1/10), L.COLOR_SUFF, L.COLOR_STOP), nil, "RIGHT")
			end
			if db.memory then
				local bli = collectgarbage("count")
				local totmem, suf = formatMemory(totmem)
				local totbli, suf2 = formatMemory(bli)
				local r, g, b = colorGradient(totmem * (1/(60*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				local r2, g2, b2 = colorGradient(bli * (1/(70*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				local r3, g3, b3 = colorGradient((totmemdiff * (1/10)) * (1/20), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				tip:SetCell(tt, col, ("|cff%02x%02x%02x%s %s%s%s"):format(r*255, g*255, b*255, totmem, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, "RIGHT")
				tip:SetCell(tt, col+1, ("|cff%02x%02x%02x%.2f %s%%%s"):format(r3*255, g3*255, b3*255, totmemdiff * (1/10), L.COLOR_SUFF, L.COLOR_STOP), nil, "RIGHT")
				tt = tt + 1
				tip:SetCell(tt, col, ("|cff%02x%02x%02x%s %s%s%s"):format(r2*255, g2*255, b2*255, totbli, L.COLOR_SUFF, suf2, L.COLOR_STOP), nil, "RIGHT")
			end
			if db.framerate or db.latency then
				tt = tt + 1
				if db.framerate then
					local framerate = GetFramerate() or 0
					tt = tt + 1
					local r, g, b = colorGradient(framerate * (1/70), 1, 0, 0, 1, 1, 0, 0, 1, 0)
					tip:SetCell(tt, db.memory and col + 1 or 5, ("|cff%02x%02x%02x%.1f %s%s%s"):format(r*255, g*255, b*255, framerate, L.COLOR_SUFF, fps_, L.COLOR_STOP), nil, "RIGHT")
				end
				if db.latency then
					local _, _, latency = GetNetStats()
					tt = tt + 1
					if type(latency) == "number" then lastlat = latency else latency = lastlat end
					local r, g, b = colorGradient(latency * (1/800), 0, 1, 0, 1, 1, 0, 1, 0, 0)
					tip:SetCell(tt, db.memory and col + 1 or 5, ("|cff%02x%02x%02x%d %sms%s"):format(r*255, g*255, b*255, latency or 0, L.COLOR_SUFF, L.COLOR_STOP), nil, "RIGHT")
				end
			end
			if db.detach then
				tip:ClearAllPoints()
				tip:SetPoint(db.detacheddata.pt, nil, db.detacheddata.rpt, db.detacheddata.x, db.detacheddata.y)
			end
			tip:Show()
		end
	end)
end

local function onLDBTextUpdate(reset)
	if reset == true then obj.text = ""; return; end
	local totcpu, totmem
	if scriptProfile or db.memory_ldb then
		if not ((isOwnTip(obj.display and obj.display.tooltip or nil) or isOwnTip(_G.GameTooltip)) and (scriptProfile or db.memory)) then
			totcpu, _, totmem = onCollect(false) -- lighten collect, don't set to true!
		else
			totcpu = lastotalcpu
			totmem = lastotalmem
		end
	end
	local var, suf, r, g, b
	if scriptProfile then
		var, suf = formatTime(totcpu)
	end
	larg1 = scriptProfile and ("%s%s%s%s%s"):format(L.COLOR_GOLD, var, L.COLOR_SUFF, suf, L.COLOR_STOP) or nil
	if db.memory_ldb then
		var, suf = formatMemory(totmem, 1)
		r, g, b = colorGradient(totmem * (1/(60*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
	end
	larg2 = db.memory_ldb and ("|cff%02x%02x%02x%s%s%s%s"):format(r*255, g*255, b*255, var, L.COLOR_SUFF, suf, L.COLOR_STOP) or nil
	if db.framerate_ldb then
		var = GetFramerate() or 0
		r, g, b = colorGradient(var * (1/70), 1, 0, 0, 1, 1, 0, 0, 1, 0)
	end
	larg3 = db.framerate_ldb and ("|cff%02x%02x%02x%.1f%s%s%s"):format(r*255, g*255, b*255, var, L.COLOR_SUFF, fps_, L.COLOR_STOP) or nil
	if db.latency_ldb then
		local _, _, f = GetNetStats()
		var = f
		if type(var) == "number" then lastlatb = var else var = lastlatb end
		r, g, b = colorGradient(var * (1/800), 0, 1, 0, 1, 1, 0, 1, 0, 0)
	end
	larg4 = db.latency_ldb and ("|cff%02x%02x%02x%d%sms%s"):format(r*255, g*255, b*255, type(var) == "number" and var or lastlatb or 0, L.COLOR_SUFF, L.COLOR_STOP) or nil
	obj.text = (larg1 and larg1.." " or "")..(larg2 and larg2.." " or "")..(larg3 and larg3.." " or "")..(larg4 and larg4.." " or "")
end

local function onGameTooltipShow(GameTip)
	if obj.onupdate or not GameTip then return end -- secure check
	if not scriptProfile and not db.memory then
		GameTip:AddLine((L.TIPHINT1):format("", " ", ""), nil, nil, nil, 1)
		GameTip:AddLine((L.TIPHINT2):format("", " ", ""), nil, nil, nil, 1)
		GameTip:AddLine((L.TIPHINT3):format("", " ", ""), nil, nil, nil, 1)
		GameTip:AddLine(" ")
		GameTip:AddLine((L.TIPHINT4):format(L.COLOR_HINT1, L.COLOR_STOP, "", " ", ""), 0.2, 1, 0.2, 1)
		GameTip:AddLine((L.TIPHINT5):format("", "", L.COLOR_HINT1, L.COLOR_STOP, "", ""), 0.2, 1, 0.2, 1)
		GameTip:Show()
		return
	end
	GameTip:AddLine(" ")
	local delay = 0
	first       = true
	obj.onupdate = CreateFrame("Frame")
	obj.onupdate:SetScript("OnUpdate", function(_, elapsed)
		delay = delay + elapsed
		if ( delay > tonumber(db.tooltipthreshold) ) or first then
			delay = 0
			if not ( scriptProfile or db.memory ) or not ( isOwnTip(GameTip) or first ) then OnLeave() return end -- secure check
			first = false
			GameTip:ClearLines()
			GameTip:AddDoubleLine(" ", L.N_TITLE) -- secure check
			GameTip:AddDoubleLine(" ", db.memory and stringRecycler("["..L.N_MEM.."]") or "[CPU]")
			GameTip:AddLine(" ")
			local totcpu, _, totmem = onCollect(true)
			table_sort(modsort, onSort)
			for k, v in pairs(modsort) do
				if db.memory then
					local str, suf = formatMemory(v.mem)
					local r, g, b  = colorGradient(v.mem * (1/(40*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
					GameTip:AddDoubleLine(stringRecycler(k..". "..v.add), ("%s %s%s%s"):format(str, L.COLOR_SUFF, suf, L.COLOR_STOP), 0, 1, 1, r, g, b)
				elseif scriptProfile then
					local str, suf = formatTime(v.cpu)
					GameTip:AddDoubleLine(stringRecycler(k..". "..v.add), ("%s %s%s%s"):format(str, L.COLOR_SUFF, suf, L.COLOR_STOP), 0, 1, 1, 0, 1, 0)
				end
				if k == db.cpukill then break end
			end
			if db.memory then
				local str, suf = formatMemory(totmem)
				local r, g, b  = colorGradient(totmem * (1/(60*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				GameTip:AddLine(" ")
				GameTip:AddDoubleLine(stringRecycler(L.COLOR_GOLD..L.N_TOT..L.COLOR_STOP), ("|cff%02x%02x%02x%s %s%s%s"):format(r*255, g*255, b*255, str, L.COLOR_SUFF, suf, L.COLOR_STOP), 1, 1, 1, 1, 1, 1)
			elseif scriptProfile then
				local data, suf = formatTime(totcpu)
				GameTip:AddLine(" ")
				GameTip:AddDoubleLine(stringRecycler(L.COLOR_GOLD..L.N_TOT..L.COLOR_STOP), ("%s%s %s%s%s"):format(L.COLOR_GOLD, data, L.COLOR_SUFF, suf, L.COLOR_STOP), 1, 1, 1, 1, 1, 1)
			end
			if db.memory then
				local bli         = collectgarbage("count")
				local totbli, suf = formatMemory(bli)
				local r, g, b     = colorGradient(bli * (1/(70*1024)), 0, 1, 0, 1, 1, 0, 1, 0, 0)
				GameTip:AddDoubleLine(stringRecycler(L.COLOR_GOLD..L.N_BLI..L.COLOR_STOP), ("%s %s%s%s"):format(totbli, L.COLOR_SUFF, suf, L.COLOR_STOP), nil, nil, nil, r, g, b)
			end
			if db.framerate or db.latency then
				GameTip:AddLine(" ")
				if db.framerate then
					local fps     = GetFramerate() or 0
					local r, g, b = colorGradient(fps * (1/70), 1, 0, 0, 1, 1, 0, 0, 1, 0)
					GameTip:AddDoubleLine(stringRecycler(L.COLOR_GOLD..L.N_FPS..L.COLOR_STOP), ("%.1f %s%s%s"):format(fps, L.COLOR_SUFF, fps_, L.COLOR_STOP), nil, nil, nil, r, g, b)
				end
				if db.latency then
					local _, _, latency = GetNetStats()
					lastlat             = type(latency) == "number" and latency or lastlat
					local r, g, b       = colorGradient(latency * (1/800), 0, 1, 0, 1, 1, 0, 1, 0, 0)
					GameTip:AddDoubleLine(stringRecycler(L.COLOR_GOLD..L.N_LAT..L.COLOR_STOP), ("%d %sms%s"):format(latency or 0, L.COLOR_SUFF, L.COLOR_STOP), nil, nil, nil, r, g, b)
				end
			end
			GameTip:Show()
		end
	end)
end

local function onToggleLDBText()
	if obj.onupdate_b then obj.onupdate_b:SetScript("OnUpdate", nil) end
	obj.onupdate_b = CreateFrame("Frame")
	if scriptProfile or db.memory_ldb or db.latency_ldb or db.framerate_ldb then
		local first, delay = true, 0
		obj.onupdate_b:SetScript("OnUpdate", function(_, elapsed)
			delay = delay + elapsed
			if delay > tonumber(db.ldbtextthreshold) or ( delay > 3 and first ) then
				delay, first = 0, false
				onLDBTextUpdate(nil)
			end
		end)
	else
		if obj.onupdate_b:GetScript("OnUpdate") then obj.onupdate_b:SetScript("OnUpdate", nil) end
		obj.onupdate_b = nil
		onLDBTextUpdate(true)
	end
end

local function onCheckStatus(c)
	local index = {
		cpukill          = "[%s%d%s]",
		ldbtextthreshold = "[%s%d%s]",
		tooltipthreshold = "[%s%.1f%s]",
		scale            = "[%s%.2f%s]"
	}
	return (index[c] and index[c] or (c and "[%sON%s]" or "[%sOFF%s]")):format(index[c] and L.COLOR_GREEN or (c and L.COLOR_GREEN or L.COLOR_RED), index[c] and db[c] or L.COLOR_STOP, L.COLOR_STOP)
end

local function onToggleMemory(c)
	local isInit = c == true
	if not isInit then
		first     = true
		db.memory = not db.memory
	else
		db.memory_ldb = not db.memory_ldb
	end
	onToggleLDBText()
	D(("%s%s%s: %s %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, not isInit and onCheckStatus(db.memory) or onCheckStatus(db.memory_ldb), ("%s %s"):format(L.N_MEM, not isInit and L.N_DAT_TIP or L.N_DAT_LDB)))
end

local function onToggleMinimap(c)
	local isInit = c == true
	if not isInit then db.minimap = not db.minimap end
	if db.minimap then
		minimap = LibStub("LibDBIcon-1.0", true) or nil
		if minimap then
			db.minimapdata            = db.minimapdata or {}
			db.minimapdata.minimapPos = db.minimapdata.minimapPos or random(0, 360)
			if not minimap:IsRegistered(L.N_TITLE) then minimap:Register(L.N_TITLE, obj, db.minimapdata) end
			minimap:Show(L.N_TITLE)
		else return not isInit and D(("%s%s%s: %s%s %s%s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, L.COLOR_GREY, L.N_MAP, L.NOT_DBICO, L.COLOR_STOP)) or nil end
	else
		if minimap and minimap:IsRegistered(L.N_TITLE) then minimap:Hide(L.N_TITLE) end
		minimap = nil
	end
	return not isInit and D(("%s%s%s: %s %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, onCheckStatus(db.minimap), L.N_MAP)) or nil
end

local function onToggleQTip(c)
	local isInit = c == true
	if not isInit then
		db.qtip = not db.qtip
		first = true
	end
	if db.qtip then
		QTip = LibStub("LibQTip-1.0", true) or nil
		if QTip then
			local GameTip = _G.GameTooltip
			if GameTip and GameTip:IsVisible() then
				if isOwnTip(GameTip) then GameTip:Hide(); OnLeave(); end
			end
			obj.OnEnter = OnQTipShow
			obj.OnTooltipShow = nil
		else
			obj.OnEnter = nil
			obj.OnTooltipShow = onGameTooltipShow
			return not isInit and D(("%s%s%s: %s%s %s%s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, L.COLOR_GREY, L.N_L_QTIP, L.NOT_QTIP, L.COLOR_STOP)) or nil
		end
	else
		if obj and obj.display then OnLeave(obj.display) end
		QTip = nil
		obj.OnEnter = nil
		obj.OnTooltipShow = onGameTooltipShow
	end
	return not isInit and D(("%s%s%s: %s %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, onCheckStatus(db.qtip), L.N_L_QTIP)) or nil
end

local function onSetMaxAddOns(c)
	first = true
	db.cpukill = c
	D(("%s%s%s: %s %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, onCheckStatus("cpukill"), L.N_MOD))
end

local function onSetScale(c)
	db.scale = c
	D(("%s%s%s: %s %s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, onCheckStatus("scale"), L.N_SC))
end

local function onToggleIcons()
	first = true
	db.icons = not db.icons
	local missingQtip = db.icons and not QTip
	D(("%s%s%s: %s %s %s%s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, missingQtip and L.COLOR_GREY or onCheckStatus(db.icons), L.N_ICO, missingQtip and L.NOT_QTIP or "", missingQtip and L.COLOR_STOP or ""))
end

local slashes = {
	Tldb   = { "ldbtextthreshold", function(c) if type(c) ~= "number" then return false end; db.ldbtextthreshold = c; return true; end,                L.TOGGLE_LDB },
	Ttip   = { "tooltipthreshold", function(c) if type(c) ~= "number" then return false end; db.tooltipthreshold = c; return true; end,                L.TOGGLE_TIP },
	fps    = { "framerate",        function() first = true; db.framerate = not db.framerate; return true; end,                                         L.TOGGLE_FPS, L.N_DAT_TIP, true },
	fps2   = { "framerate_ldb",    function() db.framerate_ldb = not db.framerate_ldb; onToggleLDBText(); return true; end,                            L.TOGGLE_FPS, L.N_DAT_LDB, true },
	garb   = { nil,                function() collectgarbage("collect"); return true; end,                                                             L.TOGGLE_GAR },
	hide   = { nil,                function() if db.detach and ( obj and obj.display ) then OnLeave(obj.display) end; return true; end,                L.TOGGLE_HID },
	ico    = { "icons",            function() onToggleIcons(); return true; end,                                                                       L.TOGGLE_ICO, L.NOT_QTIP },
	kill   = { "cpukill",          function(c) if type(c) ~= "number" then return false end; onSetMaxAddOns(c); return true; end,                      L.TOGGLE_KLL },
	lat    = { "latency",          function() first = true; db.latency = not db.latency; return true; end,                                             L.TOGGLE_LAT, L.N_DAT_TIP, true },
	lat2   = { "latency_ldb",      function() db.latency_ldb = not db.latency_ldb; onToggleLDBText(); return true; end,                                L.TOGGLE_LAT, L.N_DAT_LDB, true },
	mem    = { "memory",           function() onToggleMemory(nil); return true; end,                                                                   L.TOGGLE_MEM, L.N_DAT_TIP, true },
	mem2   = { "memory_ldb",       function() onToggleMemory(true); return true; end,                                                                  L.TOGGLE_MEM, L.N_DAT_LDB, true },
	mini   = { "minimap",          function() onToggleMinimap(); return true; end,                                                                     L.TOGGLE_MAP, L.NOT_DBICO },
	prof   = { scriptProfile,      function() if scriptProfile then SetCVar("scriptProfile", 0) else SetCVar("scriptProfile", 1) end; ReloadUI(); end, L.TOGGLE_CPU },
	qtip   = { "qtip",             function() onToggleQTip(nil); return true; end,                                                                     L.TOGGLE_L_QTIP },
	reset  = { nil,                function() BROKERCPUDB = nil; SetCVar("scriptProfile", 0); ReloadUI(); end,                                         L.TOGGLE_RST },
	scale  = { "scale",            function(c) if type(c) ~= "number" then return false end; onSetScale(c); return true; end,                          L.TOGGLE_SC, L.NOT_QTIP },
	detach = { "detach",           function()
			db.detach = not db.detach
			first = true
			if db.detach and not QTip then
				D(("%s%s%s: %s%s %s%s"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, L.COLOR_GREY, L.N_DET, L.NOT_QTIP, L.COLOR_STOP))
			end
			return true
		end, L.TOGGLE_DET, L.NOT_QTIP }
}

local function onSlash(c)
	local key, _, option = (c):match("^(%a+%d?)(%s?(.*))")
	local reply          = slashes[key] and slashes[key][2](tonumber(option)) or nil
	if not reply then -- print usage
		D(("%s%s%s:"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP))
		for option, data in pairs(slashes) do
			D(type(data[1]) == "string" and onCheckStatus(db[data[1]]) or "", ("%s%s/cpu %s%s%s%s %s"):format(data[1] and L.SLASH_SEP or "", L.COLOR_GOLD, option, L.COLOR_STOP, L.SLASH_SEP, data[5] and (data[3]):format(data[4]) or data[3], data[4] or ""))
		end
	end
end

local uidropdown = {
	{ -- Tooltips
		text = ("%s r|cff7fff7f%s|r by |cffabd473Merah|r"):format(L.N_TITLE, MINOR_VERSION), isTitle = true, notCheckable = true, keepShownOnClick = true,
	},
	{ -- Separator
		text = L.COLOR_GOLD.."--------------------|r", justifyH = "CENTER", notCheckable = true, notClickable = true, isTitle = true, keepShownOnClick = true,
	},
	{ -- Tooltips
		text = L.N_DAT_TIP, hasArrow = true, notCheckable = true, tooltipTitle = L.N_DAT_TIP, tooltipText = L.N_DAT_TIP, keepShownOnClick = true,
		menuList = {
			{
				text = L.N_MEM, tooltipTitle = L.N_MEM, tooltipText = (L.TOGGLE_MEM):format(L.N_DAT_TIP), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.mem[2], checked = function() return db.memory end,
			},
			{
				text = L.N_FPS, tooltipTitle = L.N_FPS, tooltipText = (L.TOGGLE_FPS):format(L.N_DAT_TIP), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.fps[2], checked = function() return db.framerate end,
			},
			{
				text = L.N_LAT, tooltipTitle = L.N_LAT, tooltipText = (L.TOGGLE_LAT):format(L.N_DAT_TIP), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.lat[2], checked = function() return db.latency end,
			}
		}
	},
	{ -- Broker
		text = L.N_DAT_LDB, hasArrow = true, notCheckable = true, tooltipTitle = L.N_DAT_LDB, tooltipText = L.N_DAT_LDB, keepShownOnClick = true,
		menuList = {
			{
				text = L.N_MEM, tooltipTitle = L.N_MEM, tooltipText = (L.TOGGLE_MEM):format(L.N_DAT_LDB), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.mem2[2], checked = function() return db.memory_ldb end,
			},
			{
				text = L.N_FPS, tooltipTitle = L.N_FPS, tooltipText = (L.TOGGLE_FPS):format(L.N_DAT_LDB), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.fps2[2], checked = function() return db.framerate_ldb end,
			},
			{
				text = L.N_LAT, tooltipTitle = L.N_LAT, tooltipText = (L.TOGGLE_LAT):format(L.N_DAT_LDB), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.lat2[2], checked = function() return db.latency_ldb end,
			}
		}
	},
	{ -- Cpukill
		text = L.N_MOD, hasArrow = true, tooltipTitle = L.N_MOD, tooltipText = L.TOGGLE_KLL, notCheckable = true, keepShownOnClick = true,
		menuList = {
			{ text = "10", tooltipTitle = L.N_MOD, tooltipText = "or type /cpu kill <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.kill[2](10) end },
			{ text = "30", tooltipTitle = L.N_MOD, tooltipText = "or type /cpu kill <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.kill[2](30) end },
			{ text = "50", tooltipTitle = L.N_MOD, tooltipText = "or type /cpu kill <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.kill[2](50) end }
		}
	},
	{ -- Scale
		text = L.N_SC, hasArrow = true, tooltipTitle = L.N_SC, tooltipText = ("%s %s"):format(L.TOGGLE_SC, L.NOT_QTIP), notCheckable = true, keepShownOnClick = true,
		menuList = {
			{ text = "1", tooltipTitle = L.N_SC, tooltipText = "or type /cpu scale <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.scale[2](1) end },
			{ text = "0.9", tooltipTitle = L.N_SC, tooltipText = "or type /cpu scale <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.scale[2](0.9) end },
			{ text = "0.8", tooltipTitle = L.N_SC, tooltipText = "or type /cpu scale <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.scale[2](0.8) end }
		}
	},
	{ -- Thresholds
		text = L.N_THR, hasArrow = true, tooltipTitle = L.N_THR, tooltipText = L.N_THR, notCheckable = true, keepShownOnClick = true,
		menuList = { {
			text = L.N_THR_LDB, hasArrow = true, tooltipTitle = L.N_THR_LDB, tooltipText = "or type /cpu Tldb <number>", notCheckable = true, keepShownOnClick = true,
			menuList = {
				{ text = "10", tooltipTitle = L.N_THR_LDB, tooltipText = "or type /cpu Tldb <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Tldb[2](10) end },
				{ text = "5", tooltipTitle = L.N_THR_LDB, tooltipText = "or type /cpu Tldb <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Tldb[2](5) end },
				{ text = "3", tooltipTitle = L.N_THR_LDB, tooltipText = "or type /cpu Tldb <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Tldb[2](3) end }
			} },
			{ text = L.N_THR_TIP, hasArrow = true, tooltipTitle = L.N_THR_TIP, tooltipText = "or type /cpu Ttip <number>", notCheckable = true, keepShownOnClick = true,
			menuList = {
				{ text = "1", tooltipTitle = L.N_THR_TIP, tooltipText = "or type /cpu Ttip <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Ttip[2](1) end },
				{ text = "3", tooltipTitle = L.N_THR_TIP, tooltipText = "or type /cpu Ttip <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Ttip[2](3) end },
				{ text = "5", tooltipTitle = L.N_THR_TIP, tooltipText = "or type /cpu Ttip <number>", notCheckable = true, keepShownOnClick = true, func = function() slashes.Ttip[2](5) end }
			} }
		}
	},
	{ -- Libraries
		text = L.N_LIB, hasArrow = true, tooltipTitle = L.N_LIB, tooltipText = L.N_LIB, notCheckable = true, keepShownOnClick = true,
		menuList = {
			{
				text = L.N_L_QTIP, tooltipTitle = L.N_L_QTIP, tooltipText = L.TOGGLE_L_QTIP, notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
				func = slashes.qtip[2], checked = function() return db.qtip end,
			}
		}
	},
	{ -- Separator
		text = L.COLOR_GOLD.."--------------------|r", justifyH = "CENTER", notCheckable = true, notClickable = true, keepShownOnClick = true,
	},
	{ -- Garbage
		text = L.N_GAR, tooltipTitle = L.N_GAR, tooltipText = L.TOGGLE_GAR, notCheckable = true, func = slashes.garb[2], keepShownOnClick = true,
	},
	{ -- Hide
		text = L.N_HID, tooltipTitle = L.N_HID, tooltipText = ("%s %s"):format(L.TOGGLE_HID, L.NOT_QTIP), notCheckable = true, keepShownOnClick = true, func = slashes.hide[2],
	},
	{ -- Help
		text = L.N_HELP, tooltipTitle = L.N_HELP, tooltipText = L.SHOW_HELP, notCheckable = true, func = function() onSlash("") end, keepShownOnClick = true,
	},
	{ -- Minimap
		text = L.N_MAP, tooltipTitle = L.N_DAT_LDB, tooltipText = ("%s %s"):format(L.TOGGLE_MAP, L.NOT_DBICO), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
		func = slashes.mini[2], checked = function() return db.minimap end,
	},
	{ -- Icons
		text = L.N_ICO, tooltipTitle = L.N_ICO, tooltipText = ("%s %s"):format(L.TOGGLE_ICO, L.NOT_QTIP), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
		func = slashes.ico[2], checked = function() return db.icons end,
	},
	{ -- Detach
		text = L.N_DET, tooltipTitle = L.N_DET, tooltipText = ("%s %s"):format(L.TOGGLE_DET, L.NOT_QTIP), notCheckable = nil, isNotRadio = true, keepShownOnClick = true,
		func = slashes.detach[2], checked = function() return db.detach end,
	},
	{ -- Profiling
		text = L.N_CPU, tooltipTitle = L.N_CPU, tooltipText = L.TOGGLE_CPU, keepShownOnClick = true, notCheckable = nil, isNotRadio = true,
		func = slashes.prof[2], checked = function() return scriptProfile end,
	}
}

local function onClick(self, button)
	if button == "LeftButton" then
		if IsControlKeyDown() and IsAltKeyDown() then
			if scriptProfile then SetCVar("scriptProfile", 0)
			else SetCVar("scriptProfile", 1) end
			return ReloadUI()
		elseif IsShiftKeyDown() then
			if db.detach and ( obj and obj.display ) then
				OnLeave(obj.display)
			end
			return
		elseif IsControlKeyDown() then
			collectgarbage("collect")
			D(("%s%s%s: %s collect"):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, L.N_GAR))
			return
		end
	end
	if button == "RightButton" then
		local frame = CreateFrame("Frame", "BrokerCPUDropDown", UIParent, "UIDropDownMenuTemplate")
		EasyMenu(uidropdown, frame, "cursor", 0 , 0, "MENU")
	end
end

local function OnEvent(_, _, addon)
	if isBadassToBlacklist[addon] == true then
		D(("%s%s%s: !WARNING! %s is known to conflict by using CPU/Mem APIs, Broker_CPU now hooks these APIs to ensure they are not used by others."):format(L.COLOR_ACE3, L.N_TITLE, L.COLOR_STOP, addon))
	end
	if not firstLoad then
		-- if this switch to true, the for loops used to retrieve datas will parse ALL addons, otherwise they will now only scan active addons, it has better performances than calling IsAddOnLoaded each loops
		newAddOn = true
	end
	if addon ~= "Broker_CPU" then return end
	if not firstLoad then return end
	firstLoad = false
	numTotalAddOns = GetNumAddOns()
	-- getAddOnId() -- for code profiling
	-- collectgarbage("stop") -- for code profiling
	UpdateAddOnCPUUsage    = newUpdateAddOnCPUUsage
	UpdateAddOnMemoryUsage = newUpdateAddOnMemoryUsage
	GetAddOnCPUUsage       = newGetAddOnCPUUsage
	GetAddOnMemoryUsage    = newGetAddOnMemoryUsage
	GetScriptCPUUsage      = newGetScriptCPUUsage
	ResetCPUUsage          = newResetCPUUsage
	scriptProfile          = GetCVar("scriptProfile") == "1"
	SLASH_BROKERCPU1       = "/broker_cpu"
	SLASH_BROKERCPU2       = "/brokercpu"
	SLASH_BROKERCPU3       = "/cpu"
	SlashCmdList.BROKERCPU = onSlash
	obj = LibStub("LibDataBroker-1.1"):NewDataObject(L.N_TITLE, {
		type    = "data source",
		text    = "",
		icon    = icon,
		OnClick = onClick,
		OnLeave = OnLeave,
	})
	BROKERCPUDB   = BROKERCPUDB or { -- default options
		minimap          = false,
		memory           = true,
		memory_ldb       = true,
		icons            = false,
		framerate        = true,
		framerate_ldb    = true,
		latency          = true,
		latency_ldb      = true,
		detach           = false,
		qtip             = true,
		cpukill          = 0,
		scale            = 1,
		ldbtextthreshold = 10,
		tooltipthreshold = 1,
		dewdrop          = nil -- deprecated
	}
	db = BROKERCPUDB
	onToggleLDBText()
	onToggleQTip(true)
	onToggleMinimap(true)
end

event = CreateFrame("Frame")
event:SetScript("OnEvent", OnEvent)
event:RegisterEvent("ADDON_LOADED")
